﻿using System.Linq;
using System.Collections.Generic;

namespace Godot.core
{
    using SysBindingFlags = System.Reflection.BindingFlags;

    public partial class Object 
    {
        #region Inner classes / structs
        //=============================================================================
        public enum ConnectFlags
        {
            CONNECT_DEFERRED = 1,
            CONNECT_PERSIST = 2, // hint for scene to save this connection
            CONNECT_ONESHOT = 4
        }

        public struct Connection 
        {
            public Object source;
		    public string signal;
            public Object target;
		    public string method;
		    public int flags;
		    public List<Variant> binds;
		   
            public Connection(Variant p_variant)
            {
                source = target = null;
                signal = method = null;
                flags = 0;
                binds = new List<Variant>();

                //GDictionary d = p_variant;
                //if (d.has("source"))
                //    source = d["source"];
                //if (d.has("signal"))
                //    signal = d["signal"];
                //if (d.has("target"))
                //    target=d["target"];
                //if (d.has("method"))
                //    method=d["method"];
                //if (d.has("flags"))
                //    flags=d["flags"];
                //if (d.has("binds"))
                //    binds=d["binds"];
            }

		    public static implicit operator Variant(Connection cnn)
            {
                //GDictionary d = new GDictionary();
                //d["source"] = cnn.source;
                //d["signal"] = cnn.signal;
                //d["target"] = cnn.target;
                //d["method"] = cnn.method;
                //d["flags"] = cnn.flags;
                //d["binds"] = cnn.binds;
	            return null;
            }
        }

        public class Signal 
        {
		    public struct Target 
            {
			    public uint _id;
			    public StringName method;

			    //bool operator<(const Target& p_target) const { return (_id==p_target._id)?(method<p_target.method):(_id<p_target._id); }

			    public Target(uint p_id, StringName p_method) 
                { 
                    _id = p_id; 
                    method = p_method; 
                }
		    };

		    public struct Slot 
            {
			    public Connection conn;
			    public LinkedListNode<Connection> cE;
		    };

		    public MethodInfo user;
		    public Dictionary<Target, Slot> slot_map;
		    public int locks;
	    }

        public class _ObjectSignalDisconnectData
        {
            public StringName signal;
            public Object target;
            public StringName method;
        };

        public const int NOTIFICATION_POSTINITIALIZE = 0;
		public const int NOTIFICATION_PREDELETE = 1;
        //=============================================================================
        #endregion 

        #region Properties

        public ScriptInstance script_instance { get; set; }

        #endregion

        protected virtual bool _use_builtin_script() { return false; }

        protected bool _set(StringName p_name,Variant p_property) { return false; }
	    
        protected bool _get(StringName p_name,ref Variant r_property) { return false; }

        /*protected void _set_bind(string p_set, Variant p_value) 
        {
            bool valid = false;
	        set(p_set,p_value, ref valid);
        }*/

        protected void _get_property_list(ref List<PropertyInfo> p_list) {}

        protected void _notification(int p_notification) {}

        protected static string _get_category() { return ""; }

        protected static void _get_valid_parents_static(ref List<string> p_parents) {}

        protected bool _is_gpl_reversed() { return false; }

        protected void _set_bind(string p_set, Variant p_value)
        {
            bool ret = false;
            set(p_set, p_value, ref ret); 
        }

        protected Variant _get_bind(string p_name) 
        {
            bool ret = false;
	        return get(p_name, ref ret);
        }

        protected List<Variant> _get_property_list_bind() 
        {
	        List<PropertyInfo> lpi = new List<PropertyInfo>();
	        get_property_list(ref lpi);
	        return convert_property_list(lpi);
        }

        protected List<string> _get_meta_list_bind()
        {
	        List<string> _metaret = new List<string>();
	        foreach(Variant E in __metadata.Keys)
            {
		        _metaret.Add((string)E);
	        }
	        return _metaret;
        }

        protected void _add_user_signal(string p_name, List<Variant> p_args) 
        {
	        // this version of add_user_signal is meant to be used from scripts or external apis
	        // without access to ADD_SIGNAL in bind_methods
	        // added events are per instance, as opposed to the other ones, which are global
	       
            MethodInfo mi = new MethodInfo();
	        mi.name = p_name;

	        for(int i = 0; i < p_args.Count; i++) 
            {
		        Dictionary<Variant,Variant> d = p_args[i];
		        PropertyInfo param = new PropertyInfo();
		        if (d.ContainsKey("name"))
			        param.name = d["name"];
		        if (d.ContainsKey("type"))
			        param.type = (Variant.Type)(int)d["type"];

		        mi.arguments.Add(param);
	        }
	        add_user_signal(mi);

        }

        protected Variant _emit_signal(Variant[] p_args, ref Variant.CallError r_error)
        {
            r_error.error = Variant.CallError.Error.CALL_ERROR_TOO_FEW_ARGUMENTS;

	        if(p_args.Length < 1)
                return new Variant();

	        if (p_args[0].type != Variant.Type.STRING) 
            {
		        r_error.error = Variant.CallError.Error.CALL_ERROR_INVALID_ARGUMENT;
		        r_error.argument = 0;
		        r_error.expected = Variant.Type.STRING;
		        return new Variant();
	        }

	        r_error.error = Variant.CallError.Error.CALL_OK;

	        StringName signal = p_args[0];

	        Variant[] v = new Variant[Variant.VARIANT_ARG_MAX];


	        for(int i = 0; i < System.Math.Min(5, p_args.Length - 1); i++) 
            {
		        v[i] = p_args[i + 1];
	        }

	        emit_signal(signal, v[0], v[1], v[2], v[3], v[4]);
	        return new Variant();
        }

        protected Variant _call_bind(Variant[] p_args, ref Variant.CallError r_error) 
        {
	        if (p_args.Length < 1)
            {
		        r_error.error = Variant.CallError.Error.CALL_ERROR_TOO_FEW_ARGUMENTS;
		        r_error.argument = 0;
		        return new Variant();
	        }

	        if (p_args[0].type != Variant.Type.STRING) 
            {
		        r_error.error = Variant.CallError.Error.CALL_ERROR_INVALID_ARGUMENT;
		        r_error.argument = 0;
		        r_error.expected = Variant.Type.STRING;
		        return new Variant();
	        }

	        StringName method = p_args[0];

	        return call(method, p_args.Skip(1).ToArray(), ref r_error);
        }

        protected Variant _call_deferred_bind(Variant[] p_args, ref Variant.CallError r_error) 
        {
	        if (p_args.Length < 1) 
            {
		        r_error.error = Variant.CallError.Error.CALL_ERROR_TOO_FEW_ARGUMENTS;
		        r_error.argument = 0;
		        return new Variant();
	        }

	        if (p_args[0].type != Variant.Type.STRING) 
            {
		        r_error.error = Variant.CallError.Error.CALL_ERROR_INVALID_ARGUMENT;
		        r_error.argument = 0;
		        r_error.expected = Variant.Type.STRING;
		        return new Variant();
	        }

	        r_error.error = Variant.CallError.Error.CALL_OK;

	        StringName signal = p_args[0];

	        Variant[] v = new Variant[Variant.VARIANT_ARG_MAX];

	        for(int i = 0; i < System.Math.Min(5, p_args.Length - 1); i++) 
            {
		        v[i] = p_args[i + 1];
	        }

	        call_deferred(signal, v[0], v[1], v[2], v[3], v[4]);
	        return new Variant();

        }

        protected void _change_notify(string p_what = "") {  }

        protected List<Variant> _get_signal_list() { return new List<Variant>(); }

        public static void register_custom_data_to_otdb() {}

        public Object() 
        {
            _signal_map = new Dictionary<StringName, Signal>();
	        __block_signals = false;
	        __predelete_ok = 0;
	        __instance_ID = 0;
	        __instance_ID = ObjectDB.add_instance(this);
	        __can_translate = true;
	        script_instance = null;
        }

        public void emit_signal(StringName p_name, 
            Variant p_arg1 = null, Variant p_arg2 = null, Variant p_arg3  = null, Variant p_arg4= null, Variant p_arg5 = null) 
        {

	        if (__block_signals)
		        return; //no emit, signals blocked

            Signal s = null;
            _signal_map.TryGetValue(p_name, out s);
	        if (s == null) 
            {
		        return;
	        }


            List<_ObjectSignalDisconnectData> disconnect_data = new List<_ObjectSignalDisconnectData>();


	        //copy on write will ensure that disconnecting the signal or even deleting the object will not affect the signal calling.
	        //this happens automatically and will not change the performance of calling.
	        //awesome, isn't it?
	        Dictionary<Signal.Target, Signal.Slot> slot_map = s.slot_map;

	        int ssize = slot_map.Count;

            foreach(KeyValuePair<Signal.Target, Signal.Slot> E in slot_map)
            {
		        Connection c = E.Value.conn;
		        Variant[] argptr = new Variant[5]{ p_arg1, p_arg2, p_arg3, p_arg4, p_arg5};

		        Object target;

		        target = c.target;

		        int bind_count = c.binds.Count;
		        int bind = 0;

		        for(int i = 0; bind < bind_count && i < Variant.VARIANT_ARG_MAX; i++) 
                {
			        if (argptr[i].type == Variant.Type.NIL)
                    {
				        argptr[i] = c.binds[bind];
				        bind++;
			        }
		        }

		        if ((c.flags & (int)ConnectFlags.CONNECT_DEFERRED) != 0) 
                {
			        MessageQueue.singleton.push_call(target.get_instance_ID(), c.method, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
		        } 
                else 
                {
                    Variant.CallError ce = new Variant.CallError();
			        target.call( c.method, argptr , ref ce);
		        }

		        if ((c.flags & (int)ConnectFlags.CONNECT_ONESHOT) != 0) 
                {
			        _ObjectSignalDisconnectData dd = new _ObjectSignalDisconnectData();
			        dd.signal = p_name;
			        dd.target = target;
			        dd.method = c.method;
			        disconnect_data.Add(dd);
		        }

	        }

	        while (disconnect_data.Count != 0) 
            {
		        _ObjectSignalDisconnectData dd = disconnect_data.First();
		        disconnect(dd.signal,dd.target,dd.method);
		        disconnect_data.RemoveAt(0);
	        }

        }

        public void call_deferred(StringName p_method,
            Variant p_arg1 = null, Variant p_arg2= null, Variant p_arg3 = null, Variant p_arg4 = null, Variant p_arg5 = null) 
        {
	        MessageQueue.singleton.push_call(this, p_method, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
        }

        public uint get_instance_ID() { return __instance_ID; }

        /* IAPI */
        public void set(StringName p_name, Variant p_value)
        {
            bool r_valid = false;
            set(p_name, p_value, ref r_valid);
        }

        public void set(StringName p_name, Variant p_value, ref bool r_valid)
        {
            if (__script_instance != null) 
            {
		        if (__script_instance.set(p_name,p_value)) 
                {
			        r_valid=true;
			        return;
		        }

	        }

	        //try built-in setgetter
	        {
		        if (ObjectTypeDB.set_property(this, p_name,p_value)) 
                {
			        r_valid=true;
			        return;
		        }
	        }


	        if (p_name == CoreStringNames.singleton._script) 
            {
		        set_script((Script)p_value);
                r_valid = true;
		        return;

	        } else if (p_name == CoreStringNames.singleton._meta) 
            {
		        //set_meta(p_name,p_value);
		        __metadata = p_value;
		        r_valid=true;
		        return;
	        } 
            else 
            {
		        //something inside the object... :|		
		        bool success = _setv(p_name,p_value);
		        if (success) 
                {
			        r_valid = true;
			        return;
		        }
		        setvar(p_name,p_value,ref r_valid);
	        }
        }

        public Variant get(StringName p_name)
        {
            bool valid = false;
            return get(p_name, ref valid);
        }

        public Variant get(StringName p_name, ref bool r_valid)
        {
            Variant ret = new Variant();

	        if (__script_instance != null) 
            {
		        if (__script_instance.get(p_name, ref ret)) 
                {
				    r_valid=true;
			        return ret;
		        }
	        }

	        //try built-in setgetter
	        {
		        if (ObjectTypeDB.get_property((Object)this, p_name, ref ret)) 
                {
			        r_valid = true;
			        return ret;
		        }
	        }


	        if (p_name == CoreStringNames.singleton._script) 
            {
		        ret = get_script();
		        r_valid = true;
		        return ret;
	        } 
            else if (p_name == CoreStringNames.singleton._meta) 
            {
		        ret = __metadata;
		        r_valid = true;
		        return ret;
	        } 
            else 
            {
		        //something inside the object... :|
		        bool success = _getv(p_name, ref ret);
		        if (success) 
                {
			        r_valid = true;
			        return ret;
		        }
		        
                //if nothing else, use getvar
		        return getvar(p_name,ref r_valid);
	        }
        }

        public void get_property_list(ref List<PropertyInfo> p_list, bool p_reversed = false)
        {
	        if (__script_instance != null && p_reversed) 
            {
		        p_list.Add(new PropertyInfo(Variant.Type.NIL, "Script Variables", PropertyHint.PROPERTY_HINT_NONE, "", (int) PropertyUsageFlags.PROPERTY_USAGE_CATEGORY));
		        __script_instance.get_property_list(p_list);
	        }

	        _get_property_listv(ref p_list, p_reversed);
	
	        if (!_use_builtin_script())
		        return;
		
	        if (!is_type("Script")) // can still be set, but this is for userfriendlyness
		        p_list.Add(new PropertyInfo(Variant.Type.OBJECT, "script/script", PropertyHint.PROPERTY_HINT_RESOURCE_TYPE, "Script",(int)((int)PropertyUsageFlags.PROPERTY_USAGE_DEFAULT | (int)PropertyUsageFlags.PROPERTY_USAGE_STORE_IF_NONZERO)));
	        
            if (__metadata.Count != 0)
                p_list.Add(new PropertyInfo(Variant.Type.DICTIONARY, "__meta__", PropertyHint.PROPERTY_HINT_NONE, "", (int)PropertyUsageFlags.PROPERTY_USAGE_NOEDITOR | (int)PropertyUsageFlags.PROPERTY_USAGE_STORE_IF_NONZERO));
	        
            if (__script_instance != null && !p_reversed) 
            {
		        p_list.Add(new PropertyInfo(Variant.Type.NIL, "Script Variables",PropertyHint.PROPERTY_HINT_NONE, "", (int)PropertyUsageFlags.PROPERTY_USAGE_CATEGORY));
		        __script_instance.get_property_list(p_list);
	        }	
	
        }

        public bool has_method(StringName p_method)
        {
	        if (p_method == CoreStringNames.singleton._free) 
            {
		        return true;
	        }


	        if (__script_instance != null && __script_instance.has_method(p_method)) 
            {
		        return true;
	        }

	        MethodBind method = ObjectTypeDB.get_method(get_type_name(), p_method);

	        if (method != null) 
            {
		        return true;
	        }

	        return false;
        }

        public Variant callv(StringName p_method, List<Variant> p_args) 
        {
	        if (p_args.Count == 0) 
            {
		        return call(p_method);
	        }

	        Variant.CallError ce = new Variant.CallError();
	        return call(p_method, p_args.ToArray(), ref ce);

        }

        public void notification(int p_notification, bool p_reversed = false) 
        {	
	        _notificationv(p_notification,p_reversed);
	
	        if (__script_instance != null) 
            {
		        __script_instance.notification(p_notification);
	        }
        }

        //used mainly by script, get and set all INCLUDING string
	    public virtual Variant getvar(Variant p_key, ref bool r_valid)
        {
            r_valid = false;
            return new Variant();
        }

        public virtual void setvar(Variant p_key, Variant p_value, ref bool r_valid)
        {
            r_valid = false;
        }


        /* SCRIPT */
        public bool has_meta(string p_name)
        {
            return __metadata.ContainsKey(p_name);
        }   

        public void set_meta(string p_name, Variant p_value ) 
        {
	        if (p_value.type == Variant.Type.NIL) 
            {
		        __metadata.Remove(p_name);
		        return;
	        }
	        __metadata[p_name] = p_value;
        }

        public Variant get_meta(string p_name)
        {
	        if(!__metadata.ContainsKey(p_name))
                return new Variant();
	        return __metadata[p_name];
        }

        public void get_meta_list(ref List<string> p_list) 
        {
            foreach(KeyValuePair<Variant, Variant> E in __metadata)
            {
		        p_list.Add(E.Value);
	        }
        }

        public void add_user_signal(MethodInfo p_signal) 
        {
	        if(p_signal.name == "") return;
	        if(ObjectTypeDB.has_signal(get_type_name(), p_signal.name )) return;
	        if(_signal_map.ContainsKey(p_signal.name)) return;
	        Signal s = new Signal();
	        s.user = p_signal;
	        _signal_map[p_signal.name] = s;
        }

        public void set_script(Script p_script) 
        {
	        if (__script == p_script)
		        return;

	        if (script_instance != null) 
            {
		        script_instance = null;
	        }
	
	        __script = p_script;	
	        Script s = (Script) __script;

            if (s != null /*&& s->can_instance()*/ ) 
            {
                //OBJ_DEBUG_LOCK
                script_instance = s.instance_create(this);

            }

	        _change_notify("script/script");
	        emit_signal(CoreStringNames.singleton.script_changed);
        }

        public Reference get_script()
        {
            return __script;
        }

        public void connect(StringName p_signal, Object p_to_object, StringName p_to_method, List<Variant> p_binds, int p_flags) 
        {
	        if(p_to_object == null) return;

	        Signal s = _signal_map[p_signal];

	        if (s == null) 
            {
		        bool signal_is_valid = ObjectTypeDB.has_signal(get_type_name(),p_signal);
		        if (!signal_is_valid) 
                {
			        return;
		        }
		        _signal_map[p_signal] = new Signal();
		        s = _signal_map[p_signal];
	        }

	        Signal.Target target = new Signal.Target(p_to_object.get_instance_ID(), p_to_method);
	        if (s.slot_map.ContainsKey(target)) 
            {
		        return;
	        }

	        Signal.Slot slot = new Signal.Slot();

	        Connection conn = new Connection();
	        conn.source = this;
	        conn.target = p_to_object;
	        conn.method = p_to_method;
	        conn.signal = p_signal;
	        conn.flags = p_flags;
	        conn.binds = p_binds;
	        slot.conn = conn;
	        p_to_object.__connections.AddLast(conn);
            slot.cE = p_to_object.__connections.Last;
	        s.slot_map[target] = slot;
        }

        public void disconnect(StringName p_signal, Object p_to_object, StringName p_to_method)
        {
            if(p_to_object == null) return;

	        Signal s = _signal_map[p_signal];
	        if (s == null) 
            {
		        return;
	        }

	        if (s.locks > 0) 
            {
		        return;
	        }

	        Signal.Target target = new Signal.Target(p_to_object.get_instance_ID(), p_to_method);

	        if (!s.slot_map.ContainsKey(target)) 
            {
		        return;
	        }

	        int prev = p_to_object.__connections.Count;
	        p_to_object.__connections.Remove(s.slot_map[target].cE);
	        s.slot_map.Remove(target);

	        if (s.slot_map.Count == 0 && ObjectTypeDB.has_signal(get_type_name(), p_signal )) 
            {
		        //not user signal, delete
		        _signal_map.Remove(p_signal);
	        }
        }

        public bool is_connected(StringName p_signal, Object p_to_object, StringName p_to_method)
        {
            if(p_to_object == null)
                return false;
	        
            Signal s = _signal_map[p_signal];
	        
            if (s == null) 
            {
		        bool signal_is_valid = ObjectTypeDB.has_signal(get_type_name(), p_signal);
		        if (signal_is_valid)
			        return false; 
		        return false;
	        }

	        Signal.Target target = new Signal.Target(p_to_object.get_instance_ID(), p_to_method);

	        return s.slot_map.ContainsKey(target);
        }

        public void set_block_signals(bool p_block) 
        {
	        __block_signals = p_block;
        }

        public bool is_blocking_signals()
        {
	        return __block_signals;
        }

        public void set_message_translation(bool p_enable) 
        { 
            __can_translate = p_enable; 
        }

	    public bool can_translate_messages()
        { 
            return __can_translate; 
        }

        public void property_list_changed_notify() 
        {
	        _change_notify();
        }

        public StringName XL_MESSAGE(StringName p_message)
        {
	        if (!__can_translate || TranslationServer.singleton == null)
		        return p_message;

	        return TranslationServer.singleton.translate(p_message);
        }

        public StringName tr(StringName p_message)
        {
	        return XL_MESSAGE(p_message);
        }

        public virtual Variant call(StringName p_name,
            Variant p_arg1 = null, Variant p_arg2 = null, Variant p_arg3 = null, Variant p_arg4 = null, Variant p_arg5 = null) 
        {
            List<Variant> args = new List<Variant>{p_arg1, p_arg2, p_arg3, p_arg4, p_arg5};

	        for(int i = 0; i < Variant.VARIANT_ARG_MAX; i++) 
            {
                if (args[i] == null || args[i].type == Variant.Type.NIL)
                {
                    args.RemoveRange(i, Variant.VARIANT_ARG_MAX);
                    break;
                }
	        }

	        Variant.CallError error = new Variant.CallError();

	        Variant ret = call(p_name, args.ToArray(),ref error);
	        return ret;
        }

        public virtual Variant call(StringName p_method, Variant[] p_args , ref Variant.CallError r_error)
        {
            throw new System.NotImplementedException();
        }

        public static List<Variant> convert_property_list(List<PropertyInfo> p_list) 
        {
	        List<Variant> va = new List<Variant>();
            foreach(PropertyInfo pi in p_list)
	        {
		        Dictionary<Variant,Variant> d = new Dictionary<Variant,Variant>();
		        d["name"] = pi.name;
		        d["type"] = (int)pi.type;
		        d["hint"] = (int)pi.hint;
		        d["hint_string"] = pi.hint_string;
		        d["usage"] = pi.usage;
		        va.Add(d);
	        }
	        return va;
        }

        protected static void _bind_methods()
        {
            ObjectTypeDB.bind_method("get_type", typeof(Object).GetMethod("get_type"));
            ObjectTypeDB.bind_method("is_type", typeof(Object).GetMethod("is_type"));       
            ObjectTypeDB.bind_method("set", typeof(Object).GetMethod("_set_bind", SysBindingFlags.Instance | SysBindingFlags.NonPublic));
            ObjectTypeDB.bind_method("get", typeof(Object).GetMethod("_get_bind", SysBindingFlags.Instance | SysBindingFlags.NonPublic));
            ObjectTypeDB.bind_method("get_property_list", typeof(Object).GetMethod("_get_property_list_bind", SysBindingFlags.Instance | SysBindingFlags.NonPublic));
            ObjectTypeDB.bind_method("notification", typeof(Object).GetMethod("notification"), false);
            ObjectTypeDB.bind_method("get_instance_ID", typeof(Object).GetMethod("get_instance_ID"));

            ObjectTypeDB.bind_method("set_script", typeof(Object).GetMethod("set_script"));
            ObjectTypeDB.bind_method("get_script:Script", typeof(Object).GetMethod("get_script"));

            ObjectTypeDB.bind_method("set_meta", typeof(Object).GetMethod("set_meta"));
            ObjectTypeDB.bind_method("get_meta", typeof(Object).GetMethod("get_meta"));
            ObjectTypeDB.bind_method("has_meta", typeof(Object).GetMethod("has_meta"));
            ObjectTypeDB.bind_method("get_meta_list", typeof(Object).GetMethod("_get_meta_list_bind", SysBindingFlags.Instance | SysBindingFlags.NonPublic));

            ObjectTypeDB.bind_method("add_user_signal", typeof(Object).GetMethod("_add_user_signal", SysBindingFlags.Instance | SysBindingFlags.NonPublic), new List<Variant>());

            {
                MethodInfo mi = new MethodInfo();
                mi.name = "emit_signal";
                mi.arguments.Add(new PropertyInfo(Variant.Type.STRING, "signal"));
                List<Variant> defargs = new List<Variant>();
                for (int i = 0; i < Variant.VARIANT_ARG_MAX; i++)
                {
                    mi.arguments.Add(new PropertyInfo(Variant.Type.NIL, "arg" + i));
                    defargs.Add(new Variant());
                }

                ObjectTypeDB.bind_native_method<Object>(MethodFlags.METHOD_FLAGS_DEFAULT, "emit_signal", 
                    typeof(Object).GetMethod("_emit_signal", SysBindingFlags.Instance | SysBindingFlags.NonPublic), mi, defargs);
            }

            {
                MethodInfo mi = new MethodInfo();
                mi.name = "call";
                mi.arguments.Add(new PropertyInfo(Variant.Type.STRING, "method"));
                List<Variant> defargs = new List<Variant>();
                for (int i = 0; i < Variant.VARIANT_ARG_MAX; i++)
                {
                    mi.arguments.Add(new PropertyInfo(Variant.Type.NIL, "arg" + i));
                    defargs.Add(new Variant());
                }

                ObjectTypeDB.bind_native_method<Object>(MethodFlags.METHOD_FLAGS_DEFAULT, "call",
                    typeof(Object).GetMethod("_call_bind", SysBindingFlags.Instance | SysBindingFlags.NonPublic), mi, defargs);
            }

            {
                MethodInfo mi = new MethodInfo();
                mi.name = "call_deferred";
                mi.arguments.Add(new PropertyInfo(Variant.Type.STRING, "method"));
                List<Variant> defargs = new List<Variant>();
                for (int i = 0; i < Variant.VARIANT_ARG_MAX; i++)
                {
                    mi.arguments.Add(new PropertyInfo(Variant.Type.NIL, "arg" + i));
                    defargs.Add(new Variant());
                }

                ObjectTypeDB.bind_native_method<Object>(MethodFlags.METHOD_FLAGS_DEFAULT, "call_deferred",
                    typeof(Object).GetMethod("_call_deferred_bind", SysBindingFlags.Instance | SysBindingFlags.NonPublic), mi, defargs);
            }

            ObjectTypeDB.bind_method("callv:var", typeof(Object).GetMethod("callv"));

            ObjectTypeDB.bind_method("has_method", typeof(Object).GetMethod("has_method"));

            ObjectTypeDB.bind_method("get_signal_list", typeof(Object).GetMethod("_get_signal_list", SysBindingFlags.Instance | SysBindingFlags.NonPublic));

            ObjectTypeDB.bind_method("connect", typeof(Object).GetMethod("connect"), new List<Variant>());
            ObjectTypeDB.bind_method("disconnect", typeof(Object).GetMethod("disconnect"));
            ObjectTypeDB.bind_method("is_connected", typeof(Object).GetMethod("is_connected"));

            ObjectTypeDB.bind_method("set_block_signals", typeof(Object).GetMethod("set_block_signals"));
            ObjectTypeDB.bind_method("is_blocking_signals", typeof(Object).GetMethod("is_blocking_signals"));
            ObjectTypeDB.bind_method("set_message_translation", typeof(Object).GetMethod("set_message_translation"));
            ObjectTypeDB.bind_method("can_translate_messages", typeof(Object).GetMethod("set_message_translation"));
            ObjectTypeDB.bind_method("property_list_changed_notify", typeof(Object).GetMethod("property_list_changed_notify"));

            ObjectTypeDB.bind_method("XL_MESSAGE", typeof(Object).GetMethod("XL_MESSAGE"));
            ObjectTypeDB.bind_method("tr", typeof(Object).GetMethod("tr"));

            ObjectTypeDB.add_signal(get_type_static(), new MethodInfo("script_changed"));

            ObjectTypeDB.bind_integer_constant(get_type_static(), "NOTIFICATION_POSTINITIALIZE", (int)Object.NOTIFICATION_POSTINITIALIZE);
            ObjectTypeDB.bind_integer_constant(get_type_static(), "NOTIFICATION_PREDELETE", (int)Object.NOTIFICATION_PREDELETE);

            ObjectTypeDB.bind_integer_constant(get_type_static(), "CONNECT_DEFERRED", (int)ConnectFlags.CONNECT_DEFERRED);
            ObjectTypeDB.bind_integer_constant(get_type_static(), "CONNECT_PERSIST", (int)ConnectFlags.CONNECT_PERSIST);
            ObjectTypeDB.bind_integer_constant(get_type_static(), "CONNECT_ONESHOT", (int)ConnectFlags.CONNECT_ONESHOT);
        }

        private bool __predelete() 
        {
            __predelete_ok = 1;
	        notification(NOTIFICATION_PREDELETE,true);
            return (__predelete_ok != 0);
        }

        private void __postinitialize() 
        {	
	        _initialize_typev();
	        notification(NOTIFICATION_POSTINITIALIZE);	
        }

        public static bool predelete_handler(Object p_object)
        {
            if (p_object == null) return false;
            return p_object.__predelete();
        }

        public static void postinitialize_handler(Object p_obj)
        {
            if (p_obj == null) return;
            p_obj.__postinitialize();
        }

        private int __predelete_ok;
        private bool __can_translate;
        private bool __block_signals;
        private uint __instance_ID;
        private Reference __script;
        private ScriptInstance __script_instance;
        private Dictionary<Variant, Variant> __metadata;
        private Dictionary<StringName, Signal> _signal_map;
        private LinkedList<Connection> __connections;
    }

    public partial class Object
    {
        #region OBJ_TYPE( Object, N/A )

        /* TYPE API */
        public virtual string get_type() { return "Object"; }
       
        public virtual string get_save_type() { return get_type(); } //type stored when saving

        public virtual StringName get_type_name() { return (StringName)"Object"; }
        
        public virtual bool is_type(string p_type) { return (p_type == "Object"); }

        protected virtual void _initialize_typev() { Object.initialize_type(); }

        protected virtual bool _setv(StringName p_name, Variant p_property) { return false; }

        protected virtual bool _getv(StringName p_name, ref Variant r_property) { return false; }

        protected virtual void _get_property_listv(ref List<PropertyInfo> p_list, bool p_reversed) { }

        protected virtual void _notificationv(int p_notification, bool p_reversed) { }

        public static string get_type_static() { return "Object"; }
        
        public static string get_parent_type_static() { return null; }
       
        public static void get_inheritance_list_static(ref List<string> r_inheritance_list) { r_inheritance_list.Add("Object"); }
      
        public static string get_category_static() { return ""; }
     
        public static string inherits_static() { return ""; }

        public static void get_valid_parents_static(ref List<string> p_parents) {}

        public static void initialize_type() 
        {	        
	        if (__initialized)
		        return; 
            ObjectTypeDB._add_type<Object>();
	        _bind_methods();
	        __initialized=true;
        }
 
        private static bool __initialized = false;
       
        #endregion

        //===========================================================
        public static string _MD(string m_name, params object[] list)
        {
            return m_name;
        }
    }
}
